package com.guohengspace.mq.service.impl;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.guohengspace.mq.common.ResponseResult;
import com.guohengspace.mq.dao.FilesMapper;
import com.guohengspace.mq.dao.entity.Files;
import com.guohengspace.mq.model.query.*;
import com.guohengspace.mq.model.vo.CheckFileMd5Vo;
import com.guohengspace.mq.service.EcsImportImageService;
import com.guohengspace.mq.service.OssUploadPartService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;

import java.io.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSSException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Slf4j
@Service
public class OssUploadPartServiceImpl implements OssUploadPartService {
    @Value("${endpoint:xxx}")
    private String endpoint ;

    @Value("${eip.regionId}")
    String regionId;

    private static final  String bucketName = "guohengbucket";

    @Resource
    EcsImportImageService ecsImportImageService ;

    @Resource
    private FilesMapper filesMapper ;

    private ResponseResult<?> uploadBreakpoint(UploadBreakpointParam param) {
// 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, param.getAccessKeyId(), param.getAccessKeySecret());
       try {
           ObjectMetadata meta = new ObjectMetadata();
// 指定上传的内容类型。
           meta.setContentType("text/plain");
// 通过UploadFileRequest设置多个参数。
           UploadFileRequest uploadFileRequest = new UploadFileRequest( param.getBucketName(),param.getFileName());
// 通过UploadFileRequest设置单个参数。
// 设置存储空间名称。
//uploadFileRequest.setBucketName("<yourBucketName>");
// 设置文件名称。
//uploadFileRequest.setKey("<yourObjectName>");
// 指定上传的本地文件。
           uploadFileRequest.setUploadFile("<yourLocalFile>");
// 指定上传并发线程数，默认为1。
           uploadFileRequest.setTaskNum(5);
// 指定上传的分片大小，范围为100KB~5GB，默认为文件大小/10000。
           uploadFileRequest.setPartSize(param.getCurPartSize());
// 开启断点续传，默认关闭。
           uploadFileRequest.setEnableCheckpoint(true);
// 记录本地分片上传结果的文件。开启断点续传功能时需要设置此参数，上传过程中的进度信息会保存在该文件中，如果某一分片上传失败，再次上传时会根据文件中记录的点继续上传。上传
           //完成后，该文件会被删除。默认与待上传的本地文件同目录，为uploadFile.ucp。
           uploadFileRequest.setCheckpointFile(param.getFileName());
// 文件的元数据。
           uploadFileRequest.setObjectMetadata(meta);
// 设置上传成功回调，参数为Callback类型。
//        uploadFileRequest.setCallback("<yourCallbackEvent>");
// 断点续传上传。
           UploadFileResult uploadFileResult = ossClient.uploadFile(uploadFileRequest);
           String eTag = uploadFileResult.getMultipartUploadResult().getETag();
           return ResponseResult.ok(eTag);
       }catch (Throwable e){

       } finally {
           if(ossClient!=null){
               // 关闭OSSClient。
               ossClient.shutdown();
           }
       }

        return null;
    }

    public PartListing  listpartsrequest(String uploadId,String accessKeyId,String accessKeySecret){
        Files files = filesMapper.selectOne(new LambdaQueryWrapper<Files>()
                .eq(Files::getUploadId,uploadId)
                .eq(Files::getAccessKeyId,accessKeyId)
                .eq(Files::getAccessKeySecret,accessKeySecret)
        );
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        ListPartsRequest listPartsRequest = new ListPartsRequest(files.getBucketName(), files.getFileName(), files.getUploadId());
        listPartsRequest.setUploadId(files.getUploadId());
        PartListing partListing = ossClient.listParts(listPartsRequest);
        List<PartSummary> parts = partListing.getParts();
        for (PartSummary part : parts) {
            String eTag = part.getETag();
            int partNumber = part.getPartNumber();
            long size = part.getSize();

//            private int partNumber;
//            private String eTag;
//            private long partSize;
//            private Long partCRC;
        }
        return partListing ;
    }

    public ResponseResult<CheckFileMd5Vo> checkFileMd5(CheckFileMd5Param param){
        Files files = filesMapper.fileIsExistByMd5(param.getFileMd5());
        CheckFileMd5Vo checkFileMd5Vo = new CheckFileMd5Vo();
        if(files==null){
            files = new Files();
            files.setFileName(param.getFileName());
            files.setMd5(param.getFileMd5());
            files.setCreatedTime(LocalDateTime.now());
            files.setArchitecture(param.getArchitecture());
            files.setDescription(param.getDescription());
            files.setOSType(param.getOSType());
            files.setPlatform(param.getPlatform());
            filesMapper.insert(files);
            String  uploadId = "xxx"; //initiateMultipartUpload(param.getAccessKeyId(), param.getAccessKeySecret(), param.getFileName());
            files.setUploadId(uploadId);
        }
//        OSS ossClient = new OSSClientBuilder().build(endpoint, param.getAccessKeyId(), param.getAccessKeySecret());
//        ListPartsRequest listPartsRequest = new ListPartsRequest(files.getBucketName(), files.getFileName(), files.getUploadId());
//        listPartsRequest.setUploadId(files.getUploadId());
//        PartListing partListing = ossClient.listParts(listPartsRequest);
        checkFileMd5Vo.setCurrentPart(10);// todo
        checkFileMd5Vo.setUploadId(files.getUploadId());
        checkFileMd5Vo.setUpload(files.isUpload());
        return ResponseResult.ok(checkFileMd5Vo);
    }

    public List<Bucket>  listBuckets (String accessKeyId,String accessKeySecret) {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 判断存储空间examplebucket是否存在。如果返回值为true，则存储空间存在，如果返回值为false，则存储空间不存在。
            List<Bucket> buckets = ossClient.listBuckets();

            return buckets;
        } catch (OSSException oe) {
            log.error("{}",oe.getMessage(),oe);
            throw new RuntimeException(oe);
        } catch (ClientException ce) {
            log.error("{}",ce.getMessage(),ce);
            throw new RuntimeException(ce);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    public boolean  doesBucketExist (String accessKeyId,String accessKeySecret) {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 判断存储空间examplebucket是否存在。如果返回值为true，则存储空间存在，如果返回值为false，则存储空间不存在。
            boolean exists = ossClient.doesBucketExist(bucketName);
            System.out.println(exists);
            return exists;
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
            log.error("{}",oe.getMessage(),oe);
            throw new RuntimeException(oe);
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
            log.error("{}",ce.getMessage(),ce);
            throw new RuntimeException(ce);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    public void putBucket(String accessKeyId,String accessKeySecret) {
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 创建CreateBucketRequest对象。
            PutBucketImageRequest putBucketImageRequest = new PutBucketImageRequest(bucketName);

            // 如果创建存储空间的同时需要指定存储类型、存储空间的读写权限、数据容灾类型, 请参考如下代码。
            // 此处以设置存储空间的存储类型为标准存储为例介绍。
            //createBucketRequest.setStorageClass(StorageClass.Standard);
            // 数据容灾类型默认为本地冗余存储，即DataRedundancyType.LRS。如果需要设置数据容灾类型为同城冗余存储，请设置为DataRedundancyType.ZRS。
            //createBucketRequest.setDataRedundancyType(DataRedundancyType.ZRS);
            // 设置存储空间读写权限为公共读，默认为私有。
            //createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);

            // 在支持资源组的地域创建Bucket时，您可以为Bucket配置资源组。
            //createBucketRequest.setResourceGroupId(rsId);

            // 创建存储空间。
           ossClient.putBucketImage(putBucketImageRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
            log.error("{}",oe.getMessage(),oe);
            throw new RuntimeException(oe);
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
            log.error("{}",ce.getMessage(),ce);
            throw new RuntimeException(ce);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
    /**
     *  创建的全局唯一的 uploadId。
     * @param accessKeyId ak
     * @param accessKeySecret sk
     * @return uploadId
     */
    public String initiateMultipartUpload(String accessKeyId,String accessKeySecret,String fileName){
        OSS ossClient = null ;
        try {
            // 判断 BucketExist
            boolean exist = doesBucketExist(accessKeyId, accessKeySecret);
            if(!exist){
                // 创建 Bucket
                putBucket(accessKeyId,accessKeySecret);
            }
            //创建的全局唯一的 uploadId。
            ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, fileName);
            InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
            return upresult.getUploadId();
        }finally {
            if(ossClient!=null){
                ossClient.shutdown();
            }
        }
    }
    public ResponseResult<?> upLoadPart(UpLoadPartParam upLoadPartParam ){
        OSS ossClient = new OSSClientBuilder().build(endpoint, upLoadPartParam.getAccessKeyId(), upLoadPartParam.getAccessKeySecret());
        List<PartETag> partETags = new ArrayList<PartETag>();
        try {
            UploadPartRequest uploadPartRequest = new UploadPartRequest();
            uploadPartRequest.setBucketName(bucketName);
            uploadPartRequest.setKey(upLoadPartParam.getQqfilename());
            uploadPartRequest.setUploadId(upLoadPartParam.getUploadId());
            uploadPartRequest.setInputStream(upLoadPartParam.getQqfile().getInputStream());
// 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100KB。
            uploadPartRequest.setPartSize(upLoadPartParam.getQqchunksize());
// 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出这个范围，OSS将返回InvalidArgument的错误码。
            uploadPartRequest.setPartNumber(upLoadPartParam.getQqpartindex());
// 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
            UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
// 每次上传分片之后，OSS的返回结果会包含一个PartETag。PartETag将被保存到partETags中。
            partETags.add(uploadPartResult.getPartETag());
        }catch (Exception e){
            log.error(e.getMessage(),e);
            throw new RuntimeException(e);
        }
        finally {
            ossClient.shutdown();
        }
        return ResponseResult.ok(partETags);
    }

    public ResponseResult<?> upLoadPartV2(UpLoadPartParam upLoadPartParam ){
        OSS ossClient = new OSSClientBuilder().build(endpoint, upLoadPartParam.getAccessKeyId(), upLoadPartParam.getAccessKeySecret());
        List<PartETag> partETags = new ArrayList<>();
        try {
            UploadPartRequest uploadPartRequest = new UploadPartRequest();
            uploadPartRequest.setBucketName(bucketName);
            uploadPartRequest.setKey(upLoadPartParam.getQqfilename());
            uploadPartRequest.setUploadId(upLoadPartParam.getUploadId());
            uploadPartRequest.setInputStream(upLoadPartParam.getQqfile().getInputStream());
// 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100KB。
            uploadPartRequest.setPartSize(upLoadPartParam.getQqchunksize());
// 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出这个范围，OSS将返回InvalidArgument的错误码。
            uploadPartRequest.setPartNumber(upLoadPartParam.getQqpartindex());
// 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
            UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
            updateFileProgress(upLoadPartParam.getUploadId(),upLoadPartParam.getAccessKeyId(),upLoadPartParam.getAccessKeySecret(),0,upLoadPartParam.getQqpartindex());
            //todo 记录分片进度
// 每次上传分片之后，OSS的返回结果会包含一个PartETag。PartETag将被保存到partETags中。
            partETags.add(uploadPartResult.getPartETag());
            if(upLoadPartParam.getQqtotalparts()== upLoadPartParam.getQqpartindex()){
                //todo 分片上传完成 记录状态
                List<PartETag> totalpartETags = upLoadPartParam.getPartETags();
                totalpartETags.addAll(partETags);
                CompleteMultipartUploadRequest completeMultipartUploadRequest =
                        new CompleteMultipartUploadRequest(bucketName, upLoadPartParam.getQqfilename(), upLoadPartParam.getUploadId(), totalpartETags);
                updateFileProgress(upLoadPartParam.getUploadId(),upLoadPartParam.getAccessKeyId(),upLoadPartParam.getAccessKeySecret(),1,upLoadPartParam.getQqpartindex());
                // 如果需要在完成文件上传的同时设置文件访问权限，请参考以下示例代码。
                // completeMultipartUploadRequest.setObjectACL(CannedAccessControlList.PublicRead);
                // 完成上传。
                //todo 文件合并完成 记录状态
                Files files = filesMapper.selectOne(new LambdaQueryWrapper<Files>().eq(Files::getUploadId, upLoadPartParam.getUploadId()));
                ossClient.completeMultipartUpload(completeMultipartUploadRequest);
                ImportImageParam importImageParam = ImportImageParam.builder()
                        .accessKeyId(upLoadPartParam.getAccessKeyId())
                        .accessKeySecret(upLoadPartParam.getAccessKeySecret())
                        .architecture(files.getArchitecture())
                        .platform(files.getPlatform())
                        .oSType(files.getOSType())
                        .build();
                ecsImportImageService.importImage(importImageParam);
                updateFileProgress(upLoadPartParam.getUploadId(),upLoadPartParam.getAccessKeyId(),upLoadPartParam.getAccessKeySecret(),2,upLoadPartParam.getQqpartindex());
                //todo ecs上传完成 记录状态
            }
        }catch (Exception e){
            log.error(e.getMessage(),e);
            throw new RuntimeException(e);
        }
        finally {
            ossClient.shutdown();
        }
        return ResponseResult.ok(partETags);
    }

    private void updateFileProgress(String uploadId, String accessKeyId, String accessKeySecret, int status, int progress) {
        Files files = new Files();
        files.setProgress(progress);
        files.setStatus(status);
        filesMapper.update(files,new LambdaUpdateWrapper<Files>()
                .eq(Files::getUploadId,uploadId)
                .eq(Files::getAccessKeyId,accessKeyId)
                .eq(Files::getAccessKeySecret,accessKeySecret)
                .set(Files::getStatus,status)
                .set(Files::getProgress,progress)
        );
    }


    public ResponseResult<?> completeMultipartUpload(CompleteMultipartUploadParam param ){
        OSS ossClient = new OSSClientBuilder().build(endpoint, param.getAccessKeyId(), param.getAccessKeySecret());
        try {
            // 创建CompleteMultipartUploadRequest对象。
            // 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个
            //完整的文件。
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(bucketName, param.getFileName(), param.getUploadId(), param.getPartETags());
            // 如果需要在完成文件上传的同时设置文件访问权限，请参考以下示例代码。
            // completeMultipartUploadRequest.setObjectACL(CannedAccessControlList.PublicRead);
            // 完成上传。
            CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
        }finally {
            if(ossClient!=null){
                ossClient.shutdown();
            }
        }
        return ResponseResult.ok();
    }
    public void upLoad() throws IOException {
        // Endpoint以杭州为例，其它Region请按实际情况填写。
        String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
        String accessKeyId = "<yourAccessKeyId>";
        String accessKeySecret = "<yourAccessKeySecret>";
        String bucketName = "<yourBucketName>";
// <yourObjectName>表示上传文件到OSS时需要指定包含文件后缀在内的完整路径，例如abc/efg/123.jpg。
        String objectName = "<yourObjectName>";
// 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// 创建InitiateMultipartUploadRequest对象。
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest("<yourBucketName>", "<yourObjectName>");
// 如果需要在初始化分片时设置文件存储类型，请参考以下示例代码。
// ObjectMetadata metadata = new ObjectMetadata();
// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
// request.setObjectMetadata(metadata);
// 初始化分片。
        InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
// 返回uploadId，它是分片上传事件的唯一标识，您可以根据这个ID来发起相关的操作，如取消分片上传、查询分片上传等。
        String uploadId = upresult.getUploadId();
// partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
        List<PartETag> partETags = new ArrayList<PartETag>();
// 计算文件有多少个分片。
        final long partSize = 1 * 1024 * 1024L; // 1MB
        final File sampleFile = new File("<localFile>");
        long fileLength = sampleFile.length();
        int partCount = (int) (fileLength / partSize);
        if (fileLength % partSize != 0) {
            partCount++;
        }
// 遍历分片上传。
        for (int i = 0; i < partCount; i++) {
            long startPos = i * partSize;
            long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
            InputStream instream = new FileInputStream(sampleFile);
// 跳过已经上传的分片。
            instream.skip(startPos);
            UploadPartRequest uploadPartRequest = new UploadPartRequest();
            uploadPartRequest.setBucketName(bucketName);
            uploadPartRequest.setKey(objectName);
            uploadPartRequest.setUploadId(uploadId);
            uploadPartRequest.setInputStream(instream);
// 设置分片大小。除了最后一个分片没有大小限制，其他的分片最小为100KB。
            uploadPartRequest.setPartSize(curPartSize);
// 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000，如果超出这个范围，OSS将返回InvalidArgument的错误码。
            uploadPartRequest.setPartNumber(i + 1);
// 每个分片不需要按顺序上传，甚至可以在不同客户端上传，OSS会按照分片号排序组成完整的文件。
            UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
// 每次上传分片之后，OSS的返回结果会包含一个PartETag。PartETag将被保存到partETags中。
            partETags.add(uploadPartResult.getPartETag());
        }
// 创建CompleteMultipartUploadRequest对象。
// 在执行完成分片上传操作时，需要提供所有有效的partETags。OSS收到提交的partETags后，会逐一验证每个分片的有效性。当所有的数据分片验证通过后，OSS将把这些分片组合成一个
        //完整的文件。
        CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
// 如果需要在完成文件上传的同时设置文件访问权限，请参考以下示例代码。
// completeMultipartUploadRequest.setObjectACL(CannedAccessControlList.PublicRead);
// 完成上传。
        CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);

// 关闭OSSClient。
        ossClient.shutdown();
    }
}
